// ==UserScript==
// @name         Geocaching GC Code Animal Alphabet
// @namespace    https://www.geocaching.com/
// @version      1.3
// @description  Translate GC codes into animal alphabet, skipping UserSuppliedContent
// @match       https://www.geocaching.com/geocache/*
// @copyright    2026
// @author       OUT14ND3R
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    const ANIMALS = {
        A: 'Antelope',
        B: 'Badger',
        C: 'Cheetah',
        D: 'Dolphin',
        E: 'Eagle',
        F: 'Fox',
        G: 'Giraffe',
        H: 'Hippo',
        I: 'Ibex',
        J: 'Jaguar',
        K: 'Koala',
        L: 'Lemur',
        M: 'Moose',
        N: 'Narwhal',
        O: 'Otter',
        P: 'Penguin',
        Q: 'Quail',
        R: 'Raccoon',
        S: 'Shark',
        T: 'Tiger',
        U: 'Urchin',
        V: 'Vulture',
        W: 'Wolf',
        X: 'Xerus',
        Y: 'Yak',
        Z: 'Zebra'
    };

    const GC_REGEX = /\bGC[A-Z0-9]{2,10}\b/g;
    const GC_TEST_REGEX = /\bGC[A-Z0-9]{2,10}\b/;

    function animalTranslate(code) {
        const prefix = code.slice(0, 2); // Keep GC unchanged
        const rest = code.slice(2);

        const translatedRest = rest
            .toUpperCase()
            .split('')
            .map(ch => ANIMALS[ch] || ch)
            .join(' · ');

        return `${prefix} ${translatedRest}`;
    }

    function isInsideUserSuppliedContent(element) {
        while (element && element !== document.body) {
            const id = (element.id || '').toLowerCase();
            const className = (element.className || '').toString().toLowerCase();

            if (
                id.includes('usersuppliedcontent') ||
                id.includes('user-supplied-content') ||
                className.includes('usersuppliedcontent') ||
                className.includes('user-supplied-content')
            ) {
                return true;
            }

            element = element.parentElement;
        }

        return false;
    }

    function shouldSkip(node) {
        if (!node || !node.parentElement) return true;

        const parent = node.parentElement;

        // Ignore the cache owner's/user-supplied description area
        if (isInsideUserSuppliedContent(parent)) return true;

        if (
            parent.closest('.gc-animal-added') ||
            parent.closest('[data-gc-animal-processed="true"]')
        ) {
            return true;
        }

        const badTags = [
            'SCRIPT',
            'STYLE',
            'TEXTAREA',
            'INPUT',
            'SELECT',
            'OPTION',
            'BUTTON',
            'CODE',
            'PRE'
        ];

        return badTags.includes(parent.tagName);
    }

    function processTextNode(textNode) {
        if (shouldSkip(textNode)) return;

        const text = textNode.nodeValue;
        if (!GC_TEST_REGEX.test(text)) return;

        GC_REGEX.lastIndex = 0;

        const frag = document.createDocumentFragment();
        let lastIndex = 0;
        let match;

        while ((match = GC_REGEX.exec(text)) !== null) {
            const code = match[0];

            frag.appendChild(
                document.createTextNode(text.slice(lastIndex, match.index))
            );

            const original = document.createElement('span');
            original.textContent = code;
            original.dataset.gcAnimalProcessed = 'true';
            frag.appendChild(original);

            const badge = document.createElement('span');
            badge.className = 'gc-animal-added';
            badge.textContent = ` 🐾 ${animalTranslate(code)}`;
            badge.title = `Animal alphabet for ${code}`;
            frag.appendChild(badge);

            lastIndex = match.index + code.length;
        }

        frag.appendChild(document.createTextNode(text.slice(lastIndex)));
        textNode.parentNode.replaceChild(frag, textNode);
    }

    function scan(root = document.body) {
        if (!root) return;

        const walker = document.createTreeWalker(
            root,
            NodeFilter.SHOW_TEXT,
            {
                acceptNode(node) {
                    if (shouldSkip(node)) {
                        return NodeFilter.FILTER_REJECT;
                    }

                    if (GC_TEST_REGEX.test(node.nodeValue)) {
                        return NodeFilter.FILTER_ACCEPT;
                    }

                    return NodeFilter.FILTER_REJECT;
                }
            }
        );

        const nodes = [];

        while (walker.nextNode()) {
            nodes.push(walker.currentNode);
        }

        nodes.forEach(processTextNode);
    }

    function addStyles() {
        const style = document.createElement('style');
        style.textContent = `
            .gc-animal-added {
                display: inline-block;
                margin-left: 5px;
                padding: 2px 6px;
                border: 1px solid #6b8e23;
                border-radius: 8px;
                background: #f4ffe8;
                color: #234000;
                font-size: 12px;
                font-weight: bold;
                line-height: 1.4;
                vertical-align: middle;
                white-space: normal;
            }
        `;
        document.head.appendChild(style);
    }

    addStyles();

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', () => scan());
    } else {
        scan();
    }

})();